%{
/*-
 * Copyright (c) 2010-2013 Kai Wang
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include "ld.h"
#include "ld_arch.h"
#include "ld_options.h"
#include "ld_output.h"
#include "ld_script.h"
#include "ld_file.h"
#include "ld_path.h"
#include "ld_exp.h"

ELFTC_VCSID("$Id: ld_script_parser.y 2924 2013-03-17 22:53:36Z kaiwang27 $");

struct yy_buffer_state;
typedef struct yy_buffer_state *YY_BUFFER_STATE;

#ifndef	YY_BUF_SIZE
#define	YY_BUF_SIZE 16384
#endif

extern int yylex(void);
extern int yyparse(void);
extern YY_BUFFER_STATE yy_create_buffer(FILE *file, int size);
extern YY_BUFFER_STATE yy_scan_string(char *yy_str);
extern void yy_switch_to_buffer(YY_BUFFER_STATE b);
extern void yy_delete_buffer(YY_BUFFER_STATE b);
extern int lineno;
extern FILE *yyin;
extern struct ld *ld;

static void yyerror(const char *s);
static void _init_script(void);
static struct ld_script_cmd_head ldss_c, ldso_c;

%}

%token T_ABSOLUTE
%token T_ADDR
%token T_ALIGN
%token T_ALIGNOF
%token T_ASSERT
%token T_AS_NEEDED
%token T_AT
%token T_BIND
%token T_BLOCK
%token T_BYTE
%token T_CONSTANT
%token T_CONSTRUCTORS
%token T_CREATE_OBJECT_SYMBOLS
%token T_DATA_SEGMENT_ALIGN
%token T_DATA_SEGMENT_END
%token T_DATA_SEGMENT_RELRO_END
%token T_DEFINED
%token T_ENTRY
%token T_EXCLUDE_FILE
%token T_EXTERN
%token T_FILEHDR
%token T_FILL
%token T_FLAGS
%token T_FLOAT
%token T_FORCE_COMMON_ALLOCATION
%token T_GROUP
%token T_HLL
%token T_INCLUDE
%token T_INHIBIT_COMMON_ALLOCATION
%token T_INPUT
%token T_KEEP
%token T_LENGTH
%token T_LOADADDR
%token T_LONG
%token T_MAP
%token T_MAX
%token T_MEMORY
%token T_MIN
%token T_NEXT
%token T_NOCROSSREFS
%token T_NOFLOAT
%token T_OPTION
%token T_ORIGIN
%token T_OUTPUT
%token T_OUTPUT_ARCH
%token T_OUTPUT_FORMAT
%token T_PHDRS
%token T_PROVIDE
%token T_PROVIDE_HIDDEN
%token T_QUAD
%token T_REGION_ALIAS
%token T_SEARCH_DIR
%token T_SECTIONS
%token T_SEGMENT_START
%token T_SHORT
%token T_SIZEOF
%token T_SIZEOF_HEADERS
%token T_SORT_BY_ALIGNMENT
%token T_SORT_BY_NAME
%token T_SPECIAL
%token T_SQUAD
%token T_STARTUP
%token T_SUBALIGN
%token T_SYSLIB
%token T_TARGET
%token T_TRUNCATE
%token T_VER_EXTERN
%token T_VER_GLOBAL
%token T_VER_LOCAL

%token T_LSHIFT_E
%token T_RSHIFT_E
%token T_LSHIFT
%token T_RSHIFT
%token T_EQ
%token T_NE
%token T_GE
%token T_LE
%token T_ADD_E
%token T_SUB_E
%token T_MUL_E
%token T_DIV_E
%token T_AND_E
%token T_OR_E
%token T_LOGICAL_AND
%token T_LOGICAL_OR

%right '=' T_AND_E T_OR_E T_MUL_E T_DIV_E T_ADD_E T_SUB_E T_LSHIFT_E T_RSHIFT_E
%right '?' ':'
%left T_LOGICAL_OR
%left T_LOGICAL_AND
%left '|'
%left '&'
%left T_EQ T_NE T_GE T_LE '>' '<'
%left T_LSHIFT T_RSHIFT
%left '+' '-'
%left '*' '/' '%'
%left UNARY

%token <num> T_NUM
%token <str> T_COMMONPAGESIZE
%token <str> T_COPY
%token <str> T_DSECT
%token <str> T_IDENT
%token <str> T_INFO
%token <str> T_MAXPAGESIZE
%token <str> T_MEMORY_ATTR
%token <str> T_NOLOAD
%token <str> T_ONLY_IF_RO
%token <str> T_ONLY_IF_RW
%token <str> T_OVERLAY
%token <str> T_STRING
%token <str> T_WILDCARD

%type <assign> assignment
%type <assign> provide_assignment
%type <assign> provide_hidden_assignment
%type <assign> simple_assignment
%type <cmd> assert_command
%type <cmd> entry_command
%type <cmd> ldscript_command
%type <cmd> output_section_command
%type <cmd> sections_command
%type <cmd> sections_sub_command
%type <exp> expression
%type <exp> function
%type <exp> constant
%type <exp> variable
%type <exp> absolute_function
%type <exp> addr_function
%type <exp> align_function
%type <exp> alignof_function
%type <exp> block_function
%type <exp> data_segment_align_function
%type <exp> data_segment_end_function
%type <exp> data_segment_relro_end_function
%type <exp> defined_function
%type <exp> length_function
%type <exp> loadaddr_function
%type <exp> max_function
%type <exp> min_function
%type <exp> next_function
%type <exp> origin_function
%type <exp> output_section_addr
%type <exp> output_section_align
%type <exp> output_section_fillexp
%type <exp> output_section_lma
%type <exp> output_section_subalign
%type <exp> overlay_vma
%type <exp> phdr_at
%type <exp> segment_start_function
%type <exp> sizeof_function
%type <exp> sizeof_headers_function
%type <input_file> input_file
%type <input_section> input_section
%type <input_section> input_section_desc
%type <input_section> input_section_desc_no_keep
%type <list> as_needed_list
%type <list> ident_list
%type <list> ident_list_nosep
%type <list> input_file_list
%type <list> output_section_addr_and_type
%type <list> output_section_phdr
%type <list> overlay_section_list
%type <list> wildcard_list
%type <num> assign_op
%type <num> data_type
%type <num> output_section_keywords
%type <num> overlay_nocref
%type <num> phdr_filehdr
%type <num> phdr_flags
%type <num> phdr_phdrs
%type <output_data> output_section_data
%type <output_desc> output_sections_desc
%type <overlay_desc> overlay_desc
%type <overlay_section> overlay_section
%type <phdr> phdr
%type <region> memory_region
%type <str> ident
%type <str> memory_attr
%type <str> output_section_constraint
%type <str> output_section_lma_region
%type <str> output_section_region
%type <str> output_section_type
%type <str> output_section_type_keyword
%type <str> symbolic_constant
%type <str> wildcard
%type <wildcard> wildcard_sort
%type <version_entry> version_entry
%type <version_entry_head> version_block
%type <version_entry_head> version_entry_list
%type <version_entry_head> extern_block
%type <str> version_dependency

%union {
	struct ld_exp *exp;
	struct ld_script_assign *assign;
	struct ld_script_cmd *cmd;
	struct ld_script_list *list;
	struct ld_script_input_file *input_file;
	struct ld_script_phdr *phdr;
	struct ld_script_region *region;
	struct ld_script_sections_output *output_desc;
	struct ld_script_sections_output_data *output_data;
	struct ld_script_sections_output_input *input_section;
	struct ld_script_sections_overlay *overlay_desc;
	struct ld_script_sections_overlay_section *overlay_section;
	struct ld_script_version_entry *version_entry;
	struct ld_script_version_entry_head *version_entry_head;
	struct ld_wildcard *wildcard;
	char *str;
	int64_t num;
}

%%

script
	: ldscript
	|
	;

ldscript
	: ldscript_command {
		if ($1 != NULL)
			ld_script_cmd_insert(&ld->ld_scp->lds_c, $1);
	}
	| ldscript ldscript_command {
		if ($2 != NULL)
			ld_script_cmd_insert(&ld->ld_scp->lds_c, $2);
	}
	;

expression
	: expression '+' expression {
		$$ = ld_exp_binary(ld, LEOP_ADD, $1, $3);
	}
	| expression '-' expression {
		$$ = ld_exp_binary(ld, LEOP_SUBSTRACT, $1, $3);
	}
	| expression '*' expression {
		$$ = ld_exp_binary(ld, LEOP_MUL, $1, $3);
	}
	| expression '/' expression {
		$$ = ld_exp_binary(ld, LEOP_DIV, $1, $3);
	}
	| expression '%' expression {
		$$ = ld_exp_binary(ld, LEOP_MOD, $1, $3);
	}
	| expression '&' expression {
		$$ = ld_exp_binary(ld, LEOP_AND, $1, $3);
	}
	| expression '|' expression {
		$$ = ld_exp_binary(ld, LEOP_OR, $1, $3);
	}
	| expression '>' expression {
		$$ = ld_exp_binary(ld, LEOP_GREATER, $1, $3);
	}
	| expression '<' expression {
		$$ = ld_exp_binary(ld, LEOP_LESSER, $1, $3);
	}
	| expression T_EQ expression {
		$$ = ld_exp_binary(ld, LEOP_EQUAL, $1, $3);
	}
	| expression T_NE expression {
		$$ = ld_exp_binary(ld, LEOP_NE, $1, $3);
	}
	| expression T_GE expression {
		$$ = ld_exp_binary(ld, LEOP_GE, $1, $3);
	}
	| expression T_LE expression {
		$$ = ld_exp_binary(ld, LEOP_LE, $1, $3);
	}
	| expression T_LSHIFT expression {
		$$ = ld_exp_binary(ld, LEOP_LSHIFT, $1, $3);
	}
	| expression T_RSHIFT expression {
		$$ = ld_exp_binary(ld, LEOP_RSHIFT, $1, $3);
	}
	| expression T_LOGICAL_AND expression {
		$$ = ld_exp_binary(ld, LEOP_LOGICAL_AND, $1, $3);
	}
	| expression T_LOGICAL_OR expression {
		$$ = ld_exp_binary(ld, LEOP_LOGICAL_OR, $1, $3);
	}
	| '!' expression %prec UNARY {
		$$ = ld_exp_unary(ld, LEOP_NOT, $2);
	}
	| '-' expression %prec UNARY {
		$$ = ld_exp_unary(ld, LEOP_MINUS, $2);
	}
	| '~' expression %prec UNARY {
		$$ = ld_exp_unary(ld, LEOP_NEGATION, $2);
	}
	| expression '?' expression ':' expression {
		$$ = ld_exp_trinary(ld, $1, $3, $5);
	}
	| simple_assignment {
		$$ = ld_exp_assign(ld, $1);
	}
	| function
	| constant
	| variable
	| '(' expression ')' { $$ = $2;	$$->le_par = 1; }
	;

function
	: absolute_function
	| addr_function
	| align_function
	| alignof_function
	| block_function
	| data_segment_align_function
	| data_segment_end_function
	| data_segment_relro_end_function
	| defined_function
	| length_function
	| loadaddr_function
	| max_function
	| min_function
	| next_function
	| origin_function
	| segment_start_function
	| sizeof_function
	| sizeof_headers_function
	;

absolute_function
	: T_ABSOLUTE '(' expression ')' {
		$$ = ld_exp_unary(ld, LEOP_ABS, $3);
	}
	;

addr_function
	: T_ADDR '(' ident ')' {
		$$ = ld_exp_unary(ld, LEOP_ADDR, ld_exp_name(ld, $3));
	}
	;

align_function
	: T_ALIGN '(' expression ')' {
		$$ = ld_exp_unary(ld, LEOP_ALIGN, $3);
	}
	| T_ALIGN '(' expression ',' expression ')' {
		$$ = ld_exp_binary(ld, LEOP_ALIGN, $3, $5);
	}
	;

alignof_function
	: T_ALIGNOF '(' ident ')' {
		$$ = ld_exp_unary(ld, LEOP_ALIGNOF, ld_exp_name(ld, $3));
	}
	;

block_function
	: T_BLOCK '(' expression ')' {
		$$ = ld_exp_unary(ld, LEOP_BLOCK, $3);
	}
	;

data_segment_align_function
	: T_DATA_SEGMENT_ALIGN '(' expression ',' expression ')' {
		$$ = ld_exp_binary(ld, LEOP_DSA, $3, $5);
	}
	;

data_segment_end_function
	: T_DATA_SEGMENT_END '(' expression ')' {
		$$ = ld_exp_unary(ld, LEOP_DSE, $3);
	}
	;

data_segment_relro_end_function
	: T_DATA_SEGMENT_RELRO_END '(' expression ',' expression ')' {
		$$ = ld_exp_binary(ld, LEOP_DSRE, $3, $5);
	}
	;

defined_function
	: T_DEFINED '(' ident ')' {
		$$ = ld_exp_unary(ld, LEOP_DEFINED, ld_exp_symbol(ld, $3));
	}
	;

length_function
	: T_LENGTH '(' ident ')' {
		$$ = ld_exp_unary(ld, LEOP_LENGTH, ld_exp_name(ld, $3));
	}
	;

loadaddr_function
	: T_LOADADDR '(' ident ')' {
		$$ = ld_exp_unary(ld, LEOP_LOADADDR, ld_exp_name(ld, $3));
	}
	;

max_function
	: T_MAX '(' expression ',' expression ')' {
		$$ = ld_exp_binary(ld, LEOP_MAX, $3, $5);
	}
	;

min_function
	: T_MIN '(' expression ',' expression ')' {
		$$ = ld_exp_binary(ld, LEOP_MIN, $3, $5);
	}
	;

next_function
	: T_NEXT '(' expression ')' {
		$$ = ld_exp_unary(ld, LEOP_NEXT, $3);
	}
	;

origin_function
	: T_ORIGIN '(' ident ')' {
		$$ = ld_exp_unary(ld, LEOP_ORIGIN, ld_exp_name(ld, $3));
	}
	;

segment_start_function
	: T_SEGMENT_START '(' ident ',' expression ')' {
		$$ = ld_exp_binary(ld, LEOP_MIN, ld_exp_name(ld, $3), $5);
	}
	;

sizeof_function
	: T_SIZEOF '(' ident ')' {
		$$ = ld_exp_unary(ld, LEOP_SIZEOF, ld_exp_name(ld, $3));
	}
	;

sizeof_headers_function
	: T_SIZEOF_HEADERS {
		$$ = ld_exp_sizeof_headers(ld);
	}
	;

constant
	: T_NUM {
		$$ = ld_exp_constant(ld, $1);
	}
	| symbolic_constant {
		$$ = ld_exp_symbolic_constant(ld, $1);
	}
	;

symbolic_constant
	: T_CONSTANT '(' T_COMMONPAGESIZE ')' { $$ = $3; }
	| T_CONSTANT '(' T_MAXPAGESIZE ')' { $$ = $3; }
	;

ldscript_command
	: assert_command
	| assignment {
		if (*$1->lda_var->le_name == '.')
			ld_fatal(ld, "variable . can only be used inside"
			    " SECTIONS command");
		$$ = ld_script_cmd(ld, LSC_ASSIGN, $1);
	}
	| entry_command
	| extern_command { $$ = NULL; }
	| force_common_allocation_command { $$ = NULL; }
	| group_command { $$ = NULL; }
	| inhibit_common_allocation_command { $$ = NULL; }
	| input_command { $$ = NULL; }
	| memory_command { $$ = NULL; }
	| nocrossrefs_command { $$ = NULL; }
	| output_command { $$ = NULL; }
	| output_arch_command { $$ = NULL; }
	| output_format_command { $$ = NULL; }
	| phdrs_command { $$ = NULL; }
	| region_alias_command { $$ = NULL; }
	| search_dir_command { $$ = NULL; }
	| sections_command
	| startup_command { $$ = NULL; }
	| target_command { $$ = NULL; }
	| version_script_node { $$ = NULL; }
	| ';' { $$ = NULL; }
	;

assignment
	: simple_assignment
	| provide_assignment
	| provide_hidden_assignment
	;

simple_assignment
	: variable assign_op expression %prec '=' {
		$$ = ld_script_assign(ld, $1, $2, $3, 0, 0);
	}
	;

provide_assignment
	: T_PROVIDE '(' variable '=' expression ')' {
		$$ = ld_script_assign(ld, $3, LSAOP_E, $5, 1, 0);
	}
	;

provide_hidden_assignment
	: T_PROVIDE_HIDDEN '(' variable '=' expression ')' {
		$$ = ld_script_assign(ld, $3, LSAOP_E, $5, 1, 1);
	}
	;

assign_op
	: T_LSHIFT_E { $$ = LSAOP_LSHIFT_E; }
	| T_RSHIFT_E { $$ = LSAOP_RSHIFT_E; }
	| T_ADD_E { $$ = LSAOP_ADD_E; }
	| T_SUB_E { $$ = LSAOP_SUB_E; }
	| T_MUL_E { $$ = LSAOP_MUL_E; }
	| T_DIV_E { $$ = LSAOP_DIV_E; }
	| T_AND_E { $$ = LSAOP_AND_E; }
	| T_OR_E { $$ = LSAOP_OR_E; }
	| '=' { $$ = LSAOP_E; }
	;

assert_command
	: T_ASSERT '(' expression ',' T_STRING ')' {
		$$ = ld_script_assert(ld, $3, $5);
	}
	;

entry_command
	: T_ENTRY '(' ident ')' {
		$$ = ld_script_cmd(ld, LSC_ENTRY, $3);
	}
	;

extern_command
	: T_EXTERN '(' ident_list_nosep ')' { ld_script_extern(ld, $3); }
	;

force_common_allocation_command
	: T_FORCE_COMMON_ALLOCATION { ld->ld_common_alloc = 1; }
	;

group_command
	: T_GROUP '(' input_file_list ')' {
		 ld_script_group(ld, ld_script_list_reverse($3));
	}
	;

inhibit_common_allocation_command
	: T_INHIBIT_COMMON_ALLOCATION { ld->ld_common_no_alloc = 1; }
	;

input_command
	: T_INPUT '(' input_file_list ')' {
		ld_script_input(ld, ld_script_list_reverse($3));
	}
	;

memory_command
	: T_MEMORY '{' memory_region_list '}'
	;

memory_region_list
	: memory_region {
		STAILQ_INSERT_TAIL(&ld->ld_scp->lds_r, $1, ldsr_next);
	}
	| memory_region_list memory_region {
		STAILQ_INSERT_TAIL(&ld->ld_scp->lds_r, $2, ldsr_next);
	}
	;

memory_region
	: ident memory_attr ':' T_ORIGIN '=' expression ',' T_LENGTH '='
	expression {
		ld_script_region(ld, $1, $2, $6, $10);
	}
	;

memory_attr
	: T_MEMORY_ATTR
	| { $$ = NULL; }
	;

nocrossrefs_command
	: T_NOCROSSREFS '(' ident_list_nosep ')' {
		ld_script_nocrossrefs(ld, $3);
	}
	;

output_command
	: T_OUTPUT '(' ident ')' {
		if (ld->ld_output == NULL)
			ld->ld_output_file = $3;
		else
			free($3);
	}
	;

output_arch_command
	: T_OUTPUT_ARCH '(' ident ')' {
		ld_arch_set(ld, $3);
		free($3);
	}
	;

output_format_command
	: T_OUTPUT_FORMAT '(' ident ')' {
		ld_output_format(ld, $3, $3, $3);
	}
	| T_OUTPUT_FORMAT '(' ident ',' ident ',' ident ')' {
		ld_output_format(ld, $3, $5, $7);
	}
	;

phdrs_command
	: T_PHDRS '{' phdr_list '}'
	;

phdr_list
	: phdr {
		STAILQ_INSERT_TAIL(&ld->ld_scp->lds_p, $1, ldsp_next);
	}
	| phdr_list phdr {
		STAILQ_INSERT_TAIL(&ld->ld_scp->lds_p, $2, ldsp_next);
	}

phdr
	: ident ident phdr_filehdr phdr_phdrs phdr_at phdr_flags ';' {
		$$ = ld_script_phdr(ld, $1, $2, $3, $4, $5, $6);
	}
	;

phdr_filehdr
	: T_FILEHDR { $$ = 1; }
	| { $$ = 0; }
	;

phdr_phdrs
	: T_PHDRS { $$ = 1; }
	| { $$ = 0; }
	;

phdr_at
	: T_AT '(' expression ')' { $$ = $3; }
	| { $$ = NULL; }
	;

phdr_flags
	: T_FLAGS '(' T_NUM ')' { $$ = $3; }
	| { $$ = 0; }
	;

region_alias_command
	: T_REGION_ALIAS '(' ident ',' ident ')' {
		ld_script_region_alias(ld, $3, $5);
	}
	;

search_dir_command
	: T_SEARCH_DIR '(' ident ')' {
		ld_path_add(ld, $3, LPT_L);
		free($3);
	}
	;

sections_command
	: T_SECTIONS '{' sections_command_list '}' {
		struct ld_script_sections *ldss;
		ldss = malloc(sizeof(struct ld_script_sections));
		if (ldss == NULL)
			ld_fatal_std(ld, "malloc");
		memcpy(&ldss->ldss_c, &ldss_c, sizeof(ldss_c));
		$$ = ld_script_cmd(ld, LSC_SECTIONS, ldss);
		STAILQ_INIT(&ldss_c);
	}
	;

sections_command_list
	: sections_sub_command {
		if ($1 != NULL)
			ld_script_cmd_insert(&ldss_c, $1);
	}
	| sections_command_list sections_sub_command {
		if ($2 != NULL)
			ld_script_cmd_insert(&ldss_c, $2);
	}
	;

sections_sub_command
	: entry_command
	| assignment {
		$$ = ld_script_cmd(ld, LSC_ASSIGN, $1);
	}
	| output_sections_desc {
		$$ = ld_script_cmd(ld, LSC_SECTIONS_OUTPUT, $1);
	}
	| overlay_desc {
		$$ = ld_script_cmd(ld, LSC_SECTIONS_OVERLAY, $1);
	}
	| ';' { $$ = NULL; }
	;

output_sections_desc
	: ident output_section_addr_and_type ':'
	output_section_lma
	output_section_align
	output_section_subalign
	output_section_constraint
	'{' output_section_command_list '}'
	output_section_region
	output_section_lma_region
	output_section_phdr
	output_section_fillexp {
		$$ = calloc(1, sizeof(struct ld_script_sections_output));
		if ($$ == NULL)
			ld_fatal_std(ld, "calloc");
		$$->ldso_name = $1;
		$$->ldso_vma = $2->ldl_entry;
		$$->ldso_type = $2->ldl_next->ldl_entry;
		$$->ldso_lma = $4;
		$$->ldso_align = $5;
		$$->ldso_subalign = $6;
		$$->ldso_constraint = $7;
		memcpy(&$$->ldso_c, &ldso_c, sizeof(ldso_c));
		$$->ldso_region = $11;
		$$->ldso_lma_region = $12;
		$$->ldso_phdr = ld_script_list_reverse($13);
		$$->ldso_fill = $14;
		STAILQ_INIT(&ldso_c);
	}
	;

output_section_addr_and_type
	: output_section_addr output_section_type {
		$$ = ld_script_list(ld, NULL, $2);
		$$ = ld_script_list(ld, $$, $1);
	}
	| output_section_type {
		$$ = ld_script_list(ld, NULL, NULL);
		$$ = ld_script_list(ld, $$, $1);
	}
	;

output_section_addr
	: expression
	;

output_section_type
	: '(' output_section_type_keyword ')' { $$ = $2; }
	| '(' ')' { $$ = NULL; }
	| { $$ = NULL; }
	;

output_section_type_keyword
	: T_COPY
	| T_DSECT
	| T_INFO
	| T_NOLOAD
	| T_OVERLAY
	;

output_section_lma
	: T_AT '(' expression ')' { $$ = $3; }
	| { $$ = NULL; }
	;

output_section_align
	: T_ALIGN '(' expression ')' { $$ = $3; }
	| { $$ = NULL; }
	;

output_section_subalign
	: T_SUBALIGN '(' expression ')' { $$ = $3; }
	| { $$ = NULL; }
	;

output_section_constraint
	: T_ONLY_IF_RO
	| T_ONLY_IF_RW
	| { $$ = NULL; }
	;

output_section_command_list
	: output_section_command {
		if ($1 != NULL)
			ld_script_cmd_insert(&ldso_c, $1);
	}
	| output_section_command_list output_section_command {
		if ($2 != NULL)
			ld_script_cmd_insert(&ldso_c, $2);
	}
	;

output_section_command
	: assignment {
		$$ = ld_script_cmd(ld, LSC_ASSIGN, $1);
	}
	| input_section_desc {
		$$ = ld_script_cmd(ld, LSC_SECTIONS_OUTPUT_INPUT, $1);
	}
	| output_section_data {
		$$ = ld_script_cmd(ld, LSC_SECTIONS_OUTPUT_DATA, $1);
	}
	| output_section_keywords {
		$$ = ld_script_cmd(ld, LSC_SECTIONS_OUTPUT_KEYWORD,
		    (void *) (uintptr_t) $1);
	}
	| ';' { $$ = NULL; }
	;

input_section_desc
	: input_section_desc_no_keep {
		$1->ldoi_keep = 0;
		$$ = $1;
	}
	| T_KEEP '(' input_section_desc_no_keep ')' {
		$3->ldoi_keep = 0;
		$$ = $3;
	}
	;

input_section_desc_no_keep
	: wildcard_sort input_section {
		$2->ldoi_ar = NULL;
		$2->ldoi_file = $1;
		$$ = $2;
	}
	| wildcard_sort ':' wildcard_sort input_section {
		$4->ldoi_ar = $1;
		$4->ldoi_ar = $3;
		$$ = $4;
	}
	;

input_section
	: '(' T_EXCLUDE_FILE '(' wildcard_list ')' wildcard_list ')' {
		$$ = calloc(1, sizeof(struct ld_script_sections_output_input));
		if ($$ == NULL)
			ld_fatal_std(ld, "calloc");
		$$->ldoi_exclude = ld_script_list_reverse($4);
		$$->ldoi_sec = ld_script_list_reverse($6);
	}
	| '(' wildcard_list ')' {
		$$ = calloc(1, sizeof(struct ld_script_sections_output_input));
		if ($$ == NULL)
			ld_fatal_std(ld, "calloc");
		$$->ldoi_exclude = NULL;
		$$->ldoi_sec = ld_script_list_reverse($2);
	}
	;

output_section_data
	: data_type '(' expression ')' {
		$$ = calloc(1, sizeof(struct ld_script_sections_output_data));
		if ($$ == NULL)
			ld_fatal_std(ld, "calloc");
		$$->ldod_type = $1;
		$$->ldod_exp = $3;
	}
	;

data_type
	: T_BYTE { $$ = LSODT_BYTE; }
	| T_SHORT { $$ = LSODT_SHORT; }
	| T_LONG { $$ = LSODT_LONG; }
	| T_QUAD { $$ = LSODT_QUAD; }
	| T_SQUAD { $$ = LSODT_SQUAD; }
	| T_FILL { $$ = LSODT_FILL; }
	;

output_section_keywords
	: T_CREATE_OBJECT_SYMBOLS {
		$$ = LSOK_CREATE_OBJECT_SYMBOLS;
	}
	| T_CONSTRUCTORS {
		$$ = LSOK_CONSTRUCTORS;
	}
	| T_SORT_BY_NAME '(' T_CONSTRUCTORS ')' {
		$$ = LSOK_CONSTRUCTORS_SORT_BY_NAME;
	}
	;

output_section_region
	: '>' ident { $$ = $2; }
	| { $$ = NULL; }
	;

output_section_lma_region
	: T_AT '>' ident { $$ = $3; }
	| { $$ = NULL; }
	;

output_section_phdr
	: output_section_phdr ':' ident {
		$$ = ld_script_list(ld, $$, $3);
	}
	| { $$ = NULL; }
	;


output_section_fillexp
	: '=' expression { $$ = $2; }
	| { $$ = NULL; }
	;

overlay_desc
	: T_OVERLAY
	overlay_vma ':'
	overlay_nocref
	output_section_lma
	'{' overlay_section_list '}'
	output_section_region
	output_section_phdr
	output_section_fillexp {
		$$ = calloc(1, sizeof(struct ld_script_sections_overlay));
		if ($$ == NULL)
			ld_fatal_std(ld, "calloc");
		$$->ldso_vma = $2;
		$$->ldso_nocrossref = !!$4;
		$$->ldso_lma = $5;
		$$->ldso_s = $7;
		$$->ldso_region = $9;
		$$->ldso_phdr = $10;
		$$->ldso_fill = $11;
	}
	;

overlay_vma
	: expression
	| { $$ = NULL; }
	;

overlay_nocref
	: T_NOCROSSREFS { $$ = 1; }
	| { $$ = 0; }
	;

overlay_section_list
	: overlay_section {
		$$ = ld_script_list(ld, NULL, $1);
	}
	| overlay_section_list overlay_section {
		$$ = ld_script_list(ld, $1, $2);
	}
	;

overlay_section
	: ident
	'{' output_section_command_list '}'
	output_section_phdr
	output_section_fillexp {
		$$ = calloc(1,
		    sizeof(struct ld_script_sections_overlay_section));
		if ($$ == NULL)
			ld_fatal_std(ld, "calloc");
		$$->ldos_name = $1;
		memcpy(&$$->ldos_c, &ldso_c, sizeof(ldso_c));
		$$->ldos_phdr = $5;
		$$->ldos_fill = $6;
		STAILQ_INIT(&ldso_c);
	}
	;

startup_command
	: T_STARTUP '(' ident ')' {
		ld_file_add_first(ld, $3, LFT_UNKNOWN);
		free($3);
	}
	;

target_command
	: T_TARGET '(' ident ')'
	;

version_script_node
	: ident extern_block version_dependency ';' {
		ld_script_version_add_node(ld, $1, $2, $3);
	}
	| ident version_block version_dependency ';' {
		ld_script_version_add_node(ld, $1, $2, $3);
	}
	| extern_block version_dependency ';' {
		ld_script_version_add_node(ld, NULL, $1, $2);
	}
	| version_block version_dependency ';' {
		ld_script_version_add_node(ld, NULL, $1, $2);
	}
	;

extern_block
	: T_VER_EXTERN T_STRING version_block {
		ld_script_version_set_lang(ld, $3, $2);
		$$ = $3;
	}
	;

version_block
	: '{' version_entry_list '}' {
		$$ = $2;
		ld->ld_state.ls_version_local = 0;
	}
	;

version_entry_list
	: version_entry {
		$$ = ld_script_version_link_entry(ld, NULL, $1);
	}
	| version_entry_list version_entry {
		$$ = ld_script_version_link_entry(ld, $1, $2);
	}
	;

version_entry
	: T_VER_GLOBAL {
		ld->ld_state.ls_version_local = 0;
		$$ = NULL;
	}
	| T_VER_LOCAL {
		ld->ld_state.ls_version_local = 1;
		$$ = NULL;
	}
	| wildcard ';' {
		$$ = ld_script_version_alloc_entry(ld, $1, NULL);
	}
	| extern_block ';' {
		$$ = ld_script_version_alloc_entry(ld, NULL, $1);
	}
	;

version_dependency
	: ident
	| { $$ = NULL; }
	;

ident
	: T_IDENT
	| T_STRING
	;

variable
	: ident { $$ = ld_exp_symbol(ld, $1); }
	| '.'  { $$ = ld_exp_symbol(ld, "."); }
	;

wildcard
	: ident
	| T_WILDCARD
	| '*' { $$ = strdup("*"); }
	| '?' { $$ = strdup("?"); }
	;

wildcard_sort
	: wildcard {
		$$ = ld_wildcard_alloc(ld);
		$$->lw_name = $1;
		$$->lw_sort = LWS_NONE;
	}
	| T_SORT_BY_NAME '(' wildcard ')' {
		$$ = ld_wildcard_alloc(ld);
		$$->lw_name = $3;
		$$->lw_sort = LWS_NAME;
	}
	| T_SORT_BY_NAME '(' T_SORT_BY_NAME '(' wildcard ')' ')' {
		$$ = ld_wildcard_alloc(ld);
		$$->lw_name = $5;
		$$->lw_sort = LWS_NAME;
	}
	| T_SORT_BY_NAME '(' T_SORT_BY_ALIGNMENT '(' wildcard ')' ')' {
		$$ = ld_wildcard_alloc(ld);
		$$->lw_name = $5;
		$$->lw_sort = LWS_NAME_ALIGN;
	}
	| T_SORT_BY_ALIGNMENT '(' wildcard ')' {
		$$ = ld_wildcard_alloc(ld);
		$$->lw_name = $3;
		$$->lw_sort = LWS_ALIGN;
	}
	| T_SORT_BY_ALIGNMENT '(' T_SORT_BY_NAME '(' wildcard ')' ')' {
		$$ = ld_wildcard_alloc(ld);
		$$->lw_name = $5;
		$$->lw_sort = LWS_ALIGN_NAME;
	}
	| T_SORT_BY_ALIGNMENT '(' T_SORT_BY_ALIGNMENT '(' wildcard ')' ')' {
		$$ = ld_wildcard_alloc(ld);
		$$->lw_name = $5;
		$$->lw_sort = LWS_ALIGN;
	}
	;

ident_list
	: ident { $$ = ld_script_list(ld, NULL, $1); }
	| ident_list separator ident { $$ = ld_script_list(ld, $1, $3); }
	;

ident_list_nosep
	: ident { $$ = ld_script_list(ld, NULL, $1); }
	| ident_list_nosep ident { $$ = ld_script_list(ld, $1, $2); }
	;

input_file_list
	: input_file { $$ = ld_script_list(ld, NULL, $1); }
	| input_file_list separator input_file { $$ = ld_script_list(ld, $1, $3); }
	;

input_file
	: ident { $$ = ld_script_input_file(ld, 0, $1); }
	| as_needed_list { $$ = ld_script_input_file(ld, 1, $1); }
	;

as_needed_list
	: T_AS_NEEDED '(' ident_list ')' { $$ = $3; }
	;

wildcard_list
	: wildcard_sort { $$ = ld_script_list(ld, NULL, $1); }
	| wildcard_list wildcard_sort { $$ = ld_script_list(ld, $1, $2); }
	;

separator
	: ','
	|
	;

%%

/* ARGSUSED */
static void
yyerror(const char *s)
{

	(void) s;
	errx(1, "Syntax error in ld script, line %d\n", lineno);
}

static void
_init_script(void)
{

	STAILQ_INIT(&ldss_c);
	STAILQ_INIT(&ldso_c);
}

void
ld_script_parse(const char *name)
{
	YY_BUFFER_STATE b;

	_init_script();

	if ((yyin = fopen(name, "r")) == NULL)
		ld_fatal_std(ld, "fopen %s name failed", name);
	b = yy_create_buffer(yyin, YY_BUF_SIZE);
	yy_switch_to_buffer(b);
	if (yyparse() < 0)
		ld_fatal(ld, "unable to parse linker script %s", name);
	yy_delete_buffer(b);
}

void
ld_script_parse_internal(void)
{
	YY_BUFFER_STATE b;

	_init_script();

	assert(ld->ld_arch != NULL && ld->ld_arch->script != NULL);
	b = yy_scan_string(ld->ld_arch->script);
	yy_switch_to_buffer(b);
	if (yyparse() < 0)
		ld_fatal(ld, "unable to parse internal linker script");
	yy_delete_buffer(b);
}
